package com.netty.netNio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * nio 的 server
 */
public class nioServer {

    /**
     *  1,创建selector
     *  2，创建serverSocketChannel，并绑定监听端口。
     *  3，为channel通道绑定监听端口
     *  4，设置channel为非阻塞模式。
     *  5，将channel注册到selector
     *  6，循环channel就绪
     *  7，根据channel状态，处理对应的业务。
     */

    public void start() throws IOException {
        /**
         * 1，创建selector
         */
        Selector selector = Selector.open();

        /**
         * 2，创建ServerSocketChannel 监听端口
         */
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8001));
        /**
         * 3,将channel设置成非阻塞模式
         */
        serverSocketChannel.configureBlocking(false);

        /**
         * 4，将channel注册到selector上，channel是连接事件
         */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功！");

        /**
         *  5, 循环等待连接就绪的channel
         */

        for (;;){
            /**
             * select()方法是阻塞方法，会返回就绪（四种就绪事假）的channel个数。
             */
            int select = selector.select();
            /**
             * 如果没有就绪的继续选择channel
             */
            if (select == 0) continue;

            /**
             * 接收集合，集合中包含四个对象
             */
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            /**
             * 遍历集合
             */
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while (iterator.hasNext()){
                /**
                 *  获取selectionKey
                 */
                SelectionKey selectionKey = iterator.next();
                /**
                 *  从就绪的集合中，移除选择的channel。
                 *  因为不移除channel，即使处理完业务，每次都还会选择出来这个channel
                 */
                iterator.remove();


                /**
                 *  根据事件，选择业务。
                 * /

                 /**
                 *  如果是链接事件，进行下面的业务
                 *
                 *  启动服务器的时候，不执行该方法，只有在链接事件的时候会执行，链接之后执行一次。
                 */
                if (selectionKey.isAcceptable()){
                    System.out.println("启动服务器的时候，不执行该方法，只有在链接事件的时候会执行，链接之后执行一次。");
                    acceptHandler(serverSocketChannel,selector);
                }

                /**
                 *  如果是读事件，进行下面的业务
                 */
                if (selectionKey.isReadable()){
                    readHandler(selectionKey,selector);
                }

            }
        }
    }

    /**
     *  链接事件，处理器
     */

    private void acceptHandler(ServerSocketChannel serverSocketChannel,Selector selector) throws IOException {
        /**
         *  1，如果是链接事件，创建socketChannel
         *  2，将channel设置为非阻塞模式。
         *  3，将channel注册到selector上，监听读事件。
         *  4，回复客户端信息。
         */

        /**
         * 将接收到的客户端，注册到selector中
         * 与客户端建立的链接。
         */
        SocketChannel socketChannel = serverSocketChannel.accept();
        /**
         * 设置channel为非阻塞模式
         */
        SelectableChannel selectableChannel = socketChannel.configureBlocking(false);

        /**
         *  将channel注册到selector上，监听，可读事件
         */
        selectableChannel.register(selector,SelectionKey.OP_READ);
        System.out.println("回复客户端数据");
        /**
         *  回复客户端提示信息
         */
        socketChannel.write(Charset.forName("UTF-8").encode("服务器返回信息，建立链接"));

    }


    /**
     *  读事件处理器
     */
    private void readHandler(SelectionKey selectionKey,Selector selector) throws IOException {
        /**
         * 要从selectionKey中获取到已经就绪的channel
         */

        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

        /**
         * 只有buffer才能控制channel中的读和写
         * 创建buffer
         */

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        /**
         * 循环读取客户端请求信息
         */
        String request="";

        while (socketChannel.read(byteBuffer) > 0){
            /**
             * buffer切换为读模式
             */
            byteBuffer.flip();
            /**
             * 读取内容,进行解码
             */
            request = request+Charset.forName("UTF-8").decode(byteBuffer);

            /**
             *
             */
        }
        /**
         * channel注册到selector上，监听他的可读事件。
         */
        socketChannel.register(selector,SelectionKey.OP_READ);


        /**
         * 将客户端发送的请求信息，广播给其他客户端。模拟群聊
         */

        if (request.length() > 0){
            /**
             * 广播给其他客户端
             */
            System.out.println("接收到的数据："+request);
            broadCast(selector,socketChannel,request);
        }

    }

    /**
     * 广播给其他客户端
     * 1，获取所有链接到服务器的客户端。
     * 2，循环向所有链接的客户端发送数据。
     */
    public void broadCast(Selector selector,SocketChannel sourceChannel,String request) throws IOException {
        /**
         * 获取注册到selector中的所有channel
         */
        Set<SelectionKey> selectionSet = selector.keys();
        // 遍历集合
        for (SelectionKey selectionKey : selectionSet) {
            SelectableChannel channel = selectionKey.channel();
            /**
             *  判断是不是socketChannel,
             *  判断是不是发送消息的Channel,排除发送消息的Channel
             */
            if (channel instanceof SocketChannel && channel != sourceChannel){
                /**
                 * 将信息发送到所有客户端。
                 */
                ((SocketChannel) channel).write(Charset.forName("UTF-8").encode(request));
            }
        }
    }


    /**
     *
     */
    public static void main(String[] args) throws IOException {
        nioServer nioServer = new nioServer();
        nioServer.start();
    }
}
